package com.ideaaedi.springcloud.jd.user.config;

import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import com.ideaaedi.springcloud.jd.commonds.entity.user.bo.JwtTokenAdditionalInfoBO;
import com.ideaaedi.springcloud.jd.user.config.ext.ConfigMemoryClientDetailProvider;
import com.ideaaedi.springcloud.jd.user.config.ext.JdUserDetails;
import com.ideaaedi.springcloud.jd.user.config.ext.JdUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;

/**
 * spring-security-auth2认证（授权）服务器配置 <br />
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.A
 */
@Configuration
@EnableAuthorizationServer
@SuppressWarnings({"deprecation", "AlibabaClassNamingShouldBeCamel"})
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
    
    /**
     * access_token有效时长(默认1天)
     */
    @Value("${oauth2.access-token-validity-seconds:86400}")
    private int oauth2AccessTokenValiditySeconds;
    
    /**
     * refresh_token有效时长(默认30天)
     */
    @Value("${oauth2.refresher-token-validity-seconds:2592000}")
    private int oauth2RefreshTokenValiditySeconds;
    
    @Resource
    private TokenStore tokenStore;
    
    @Resource
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    
    @Resource
    private ClientDetailsService clientDetailsService;
    
    @Resource
    private ConfigMemoryClientDetailProvider configMemoryClientDetailProvider;
    
    @Resource
    private AuthenticationManager authenticationManager;
    
    @Resource
    private JdUserDetailsService jdUserDetailsService;
    
    /**
     * 对认证服务器本身的安全设置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                // 检验token的端点，设置为均可访问
                .checkTokenAccess("permitAll()")
                // 允许客户端以表单的形式提交鉴权数据
                .allowFormAuthenticationForClients();
    }
    
    /**
     * 配置客户端的详细信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(configMemoryClientDetailProvider);
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authorizationCodeServices(authorizationCodeServices()).tokenServices(tokenServices()).authenticationManager(authenticationManager);
    }
    
    /**
     * 授权码存储及相关功能（生成授权码、根据授权码获取认证信息）的实现
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }
    
    /**
     * token相关功能（创建token、刷新token、获取token）的实现
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);
        services.setTokenStore(tokenStore);
        services.setSupportRefreshToken(true);
        services.setReuseRefreshToken(false);
        services.setAccessTokenValiditySeconds(oauth2AccessTokenValiditySeconds);
        services.setRefreshTokenValiditySeconds(oauth2RefreshTokenValiditySeconds);
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Lists.newArrayList(new CustomTokenEnhancer(jdUserDetailsService),
                jwtAccessTokenConverter));
        services.setTokenEnhancer(tokenEnhancerChain);
        return services;
    }
    
    /**
     * 自定义token增强器。 对OAuth2AccessToken进行内容增强
     */
    @Slf4j
    public static class CustomTokenEnhancer implements TokenEnhancer {
        
        private final UserDetailsService userDetailsService;
        
        public CustomTokenEnhancer(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
        
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            Authentication userAuthentication = authentication.getUserAuthentication();
            if (userAuthentication != null) {
                Object principal = userAuthentication.getPrincipal();
                if (principal instanceof JdUserDetails) {
                    JdUserDetails user = (JdUserDetails) principal;
                    String additionalInfo = JSON.toJSONString(wrapAdditionalInfo(user));
                    log.info("additionalInfo -> {}", additionalInfo);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(JSON.parseObject(additionalInfo));
                } else {
                    String accountNo = authentication.getName();
                    log.info("support accountNo -> {}", accountNo);
                    JdUserDetails user = (JdUserDetails) userDetailsService.loadUserByUsername(accountNo);
                    String additionalInfo = JSON.toJSONString(wrapAdditionalInfo(user));
                    log.info("additionalInfo -> {}", additionalInfo);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(JSON.parseObject(additionalInfo));
                }
            } else {
                OAuth2Request oAuth2Request = authentication.getOAuth2Request();
                // 暂时只支持oauth2 grant_type=password 模式登录
                throw new UnsupportedOperationException("un-support oauth2 grant_type. curr oAuth2Request -> " + JSON.toJSONString(oAuth2Request));
                ///if (oAuth2Request != null && configMemoryClientDetailService.isAdditionalClient(oAuth2Request
                // .getClientId())) {
                ///    String clientId = oAuth2Request.getClientId();
                ///    ClientDetails clientDetails = configMemoryClientDetailService.loadClientByClientId(clientId);
                ///    if (clientDetails instanceof ExtBaseClientDetails) {
                ///        ExtBaseClientDetails extBaseClientDetails = (ExtBaseClientDetails) clientDetails;
                ///        // 附加信息
                ///        JwtTokenAdditionalInfoBO jwtTokenAdditionalInfo = new JwtTokenAdditionalInfoBO();
                ///        jwtTokenAdditionalInfo.setUserId(extBaseClientDetails.getVirtualUserId());
                ///        ///jwtTokenAdditionalInfo.setLoginScene(LoginSceneEnum.CLIENT_AK_SK); //
                // client_credentials模式登陆
                ///        ///jwtTokenAdditionalInfo.setUserType(); // client_credentials模式登陆
                ///        String additionalInfo = JSON.toJSONString(jwtTokenAdditionalInfo);
                ///        log.info("additionalInfo -> {}", additionalInfo);
                ///        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(JSON.parseObject
                // (additionalInfo));
                ///    }
                ///}
            }
            return accessToken;
        }
        
        /**
         * 附加信息
         *
         * @param user 当前用户
         *
         * @return 附加信息
         */
        @NonNull
        private JwtTokenAdditionalInfoBO wrapAdditionalInfo(JdUserDetails user) {
            JwtTokenAdditionalInfoBO jwtTokenAdditionalInfo = new JwtTokenAdditionalInfoBO();
            Long userId = user.getId();
            jwtTokenAdditionalInfo.setUserId(userId);
            jwtTokenAdditionalInfo.setLoginScene(user.getLoginScene());
            jwtTokenAdditionalInfo.setUserType(user.getType());
            return jwtTokenAdditionalInfo;
        }
    }
}